﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SaladBar.Domain.Users;
using SaladBar.Service.Repositories;
using System.Web.Security;
using System.Security.Cryptography;
using SaladBar.Domain;
using System.Web.Profile;

namespace SaladBar.Service.Services.Users
{
    public class UserService
    {
        private IRepository<User> _UserRepo;

        public UserService(IRepository<User> userRepo)
        {
            this._UserRepo = userRepo;
        }

        #region Membership Provider Methods
        public void ChangePassword(string username, string oldPassword, string newPassword)
        {
            User user = this._UserRepo.Get(u => u.Username.ToLower() == username.ToLower());
            if (this.CreatePasswordHash(oldPassword, user.PasswordSalt) != user.PasswordHash)
                throw new MembershipPasswordException("Invalid password.");
            user.PasswordHash = this.CreatePasswordHash(newPassword, user.PasswordSalt);
            this._UserRepo.Save(user);
        }

        public User Create(string username, 
            string password, 
            string email, 
            string passwordQuestion, 
            string passwordAnswer, 
            bool isApproved, 
            object providerUserKey, 
            out MembershipCreateStatus status)
        {
            User user = new User();
            user.Username = username;
            user.Email = email;
            user.PasswordQuestion = passwordQuestion;
            user.PasswordAnswer = passwordAnswer;
            user.IsApproved = isApproved;
            user.PasswordSalt = this.CreateSalt(128);
            user.PasswordHash = this.CreatePasswordHash(password, user.PasswordSalt);
            user.LastActivityDate = DateTime.Now;
            this._UserRepo.Save(user);

            status = MembershipCreateStatus.Success;

            return user;
        }

        public void ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            User user = this._UserRepo.Get(u => u.Username.ToLower() == username.ToLower());
            if (this.CreatePasswordHash(password, user.PasswordSalt) != user.PasswordHash)
                throw new MembershipPasswordException("Invalid password.");
            user.PasswordQuestion = newPasswordQuestion;
            user.PasswordAnswer = newPasswordAnswer;

            this._UserRepo.Save(user);
        }

        public void DeleteUser(string username)
        {
            User user = this._UserRepo.Get(u => u.Username.ToLower() == username.ToLower());
            this._UserRepo.Delete(user);
        }

        public PagedQueryResult<User> FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize)
        {
            return this._UserRepo.PagedQuery(u => u.Email.ToLower() == emailToMatch.ToLower(), null, pageSize, pageIndex);
        }

        public PagedQueryResult<User> FindUsersByUsername(string usernameToMatch, int pageIndex, int pageSize)
        {
            return this._UserRepo.PagedQuery(u => u.Username.ToLower() == usernameToMatch.ToLower(), null, pageSize, pageIndex);
        }

        public Domain.PagedQueryResult<User> GetAllUsers(int pageIndex, int pageSize)
        {
            return this._UserRepo.PagedQuery(u => true, null, pageSize, pageIndex);
        }

        public int GetNumberOfUsersOnline(int userIsOnlineTimeWindow)
        {
            return this._UserRepo.GetTotalCount(u => u.LastActivityDate >= DateTime.Now.AddMinutes(-1 * userIsOnlineTimeWindow));
        }

        public User GetByUsername(string username, bool userIsOnline)
        {
            User user = this._UserRepo.Get(u => u.Username.ToLower() == username.ToLower());
            if (userIsOnline)
            {
                user.LastActivityDate = DateTime.Now;
                this._UserRepo.Save(user);
            }
            return user;
        }

        public User GetById(int id, bool userIsOnline)
        {
            User user = this._UserRepo.Get(u => u.Id == id);
            if (userIsOnline)
            {
                user.LastActivityDate = DateTime.Now;
                this._UserRepo.Save(user);
            }
            return user;
        }

        public string GetUsernameByEmail(string email)
        {
            return this._UserRepo.Get(u => u.Email.ToLower() == email.ToLower()).Username;
        }

        public string ResetPassword(string username)
        {
            User user = this._UserRepo.Get(u => u.Username.ToLower() == username.ToLower());
            string newPassword = Membership.GeneratePassword(10, 2);
            user.PasswordSalt = this.CreateSalt(128);
            user.PasswordHash = this.CreatePasswordHash(newPassword, user.PasswordSalt);
            this._UserRepo.Save(user);
            return newPassword;
        }

        public void UnlockUser(string userName)
        {
            User user = this._UserRepo.Get(u => u.Username.ToLower() == userName.ToLower());
            user.IsLockedOut = false;
            this._UserRepo.Save(user);
        }

        private string CreateSalt(int size)
        {
            //Generate a cryptographic random number.
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
            byte[] buff = new byte[size];
            rng.GetBytes(buff);

            // Return a Base64 string representation of the random number.
            return Convert.ToBase64String(buff);
        }

        private string CreatePasswordHash(string pwd, string salt)
        {
            string saltAndPwd = String.Concat(pwd, salt);
            string hashedPwd = FormsAuthentication.HashPasswordForStoringInConfigFile(saltAndPwd, "sha1");

            return hashedPwd;
        }
        #endregion

        #region Profile Provider Methods
        public int DeleteInactiveUsers(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, string applicationName)
        {
            IEnumerable<User> usersToDelete = this._UserRepo.GetMany(u => 
                u.Profile.Application.Name == applicationName &&
                u.LastActivityDate <= userInactiveSinceDate && 
                (
                    (u.Profile.IsAuthenticatedProfile && authenticationOption == ProfileAuthenticationOption.Authenticated) ||
                    (!u.Profile.IsAuthenticatedProfile && authenticationOption == ProfileAuthenticationOption.Anonymous) ||
                    (authenticationOption == ProfileAuthenticationOption.All)
                )
            );
            int countToDelete = usersToDelete.Count();

            foreach (User user in usersToDelete)
            {
                this._UserRepo.Delete(user);
            }

            return countToDelete;
        }

        public int DeleteProfiles(string[] usernames, string applicationName)
        {
            List<string> lowerCaseUsernames = usernames.Select(un => un.ToLower()).ToList();
            IEnumerable<User> usersToDelete = this._UserRepo.GetMany(u => u.Profile.Application.Name == applicationName && lowerCaseUsernames.Contains(u.Username.ToLower()));
            int countToDelete = usersToDelete.Count();

            foreach (User user in usersToDelete)
            {
                this._UserRepo.Delete(user);
            }

            return countToDelete;
        }

        public int DeleteProfiles(ProfileInfoCollection profiles, string applicationName)
        {
            List<string> lowerCaseUsernames = new List<string>();

            foreach (ProfileInfo profile in profiles)
            {
                lowerCaseUsernames.Add(profile.UserName);
            }
            IEnumerable<User> usersToDelete = this._UserRepo.GetMany(u => u.Profile.Application.Name == applicationName && lowerCaseUsernames.Contains(u.Username.ToLower()));
            int countToDelete = usersToDelete.Count();

            foreach (User user in usersToDelete)
            {
                this._UserRepo.Delete(user);
            }

            return countToDelete;
        }


        public PagedQueryResult<User> FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageNumber, int pageSize, string applicationName)
        {
            return this._UserRepo.PagedQuery(u =>
                u.Profile.Application.Name == applicationName &&
                u.Username.ToLower() == usernameToMatch.ToLower() &&
                u.LastActivityDate <= userInactiveSinceDate &&
                (
                    (u.Profile.IsAuthenticatedProfile && authenticationOption == ProfileAuthenticationOption.Authenticated) ||
                    (!u.Profile.IsAuthenticatedProfile && authenticationOption == ProfileAuthenticationOption.Anonymous) ||
                    (authenticationOption == ProfileAuthenticationOption.All)
                )
            , null
            ,pageSize
            ,pageNumber);
        }

        public PagedQueryResult<User> FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageNumber, int pageSize, string applicationName)
        {
            return this._UserRepo.PagedQuery(u =>
                u.Profile.Application.Name == applicationName &&
                u.Username.ToLower() == usernameToMatch.ToLower() &&
                (
                    (u.Profile.IsAuthenticatedProfile && authenticationOption == ProfileAuthenticationOption.Authenticated) ||
                    (!u.Profile.IsAuthenticatedProfile && authenticationOption == ProfileAuthenticationOption.Anonymous) ||
                    (authenticationOption == ProfileAuthenticationOption.All)
                )
            , null
            , pageSize
            , pageNumber);
        }

        public PagedQueryResult<User> GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageNumber, int pageSize, string applicationName)
        {
            return this._UserRepo.PagedQuery(u =>
                u.Profile.Application.Name == applicationName &&
                u.LastActivityDate <= userInactiveSinceDate &&
                (
                    (u.Profile.IsAuthenticatedProfile && authenticationOption == ProfileAuthenticationOption.Authenticated) ||
                    (!u.Profile.IsAuthenticatedProfile && authenticationOption == ProfileAuthenticationOption.Anonymous) ||
                    (authenticationOption == ProfileAuthenticationOption.All)
                )
            , null
            , pageSize
            , pageNumber);
        }

        public PagedQueryResult<User> GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageNumber, int pageSize, string applicationName)
        {
            return this._UserRepo.PagedQuery(u =>
                u.Profile.Application.Name == applicationName &&
                (
                    (u.Profile.IsAuthenticatedProfile && authenticationOption == ProfileAuthenticationOption.Authenticated) ||
                    (!u.Profile.IsAuthenticatedProfile && authenticationOption == ProfileAuthenticationOption.Anonymous) ||
                    (authenticationOption == ProfileAuthenticationOption.All)
                )
            , null
            , pageSize
            , pageNumber);
        }

        public int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, string applicationName)
        {
            return this._UserRepo.GetTotalCount(u =>
                u.Profile.Application.Name == applicationName &&
                u.LastActivityDate <= userInactiveSinceDate &&
                (
                    (u.Profile.IsAuthenticatedProfile && authenticationOption == ProfileAuthenticationOption.Authenticated) ||
                    (!u.Profile.IsAuthenticatedProfile && authenticationOption == ProfileAuthenticationOption.Anonymous) ||
                    (authenticationOption == ProfileAuthenticationOption.All)
                )
            );
        }
        #endregion
    }
}
